﻿/*
Copyright (c) 2010 Lance Ward
This software is licensed under the MIT license, except where otherwise noted.
For complete license details, refer to license.txt
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace ADNLib
{
	public enum SizeDirection : int
	{
		NorthWest = 0,
		North = 1,
		NorthEast = 2,
		West = 3,
		East = 4,
		SouthWest = 5,
		South = 6,
		SouthEast = 7,
        None = -1
	}

	[XmlInclude(typeof(ShapeRectangle))]
	[XmlInclude(typeof(ShapeEllipse))]
	[XmlInclude(typeof(ShapeLine))]
	[XmlInclude(typeof(ShapeFreeHand))]
	[XmlInclude(typeof(ShapeText))]
	[XmlInclude(typeof(ShapeSelect))]
	[XmlInclude(typeof(ShapeBezier))]
	[XmlInclude(typeof(ShapeShadow))]
	[XmlInclude(typeof(ShapeSpray))]
    [XmlInclude(typeof(ShapeFivePtStar))]
    [XmlInclude(typeof(ShapeSixPtStar))]
    [XmlInclude(typeof(ShapeHexagon))]
    [XmlInclude(typeof(ShapePentagon))]
    [XmlInclude(typeof(ShapeOctagon))]
    [XmlInclude(typeof(ShapeRightTriangle))]
    [XmlInclude(typeof(ShapeIsoscelesTriangle))]
    [XmlInclude(typeof(ShapeParallelogram))]
    [XmlInclude(typeof(ShapeTrapezoid))]

    //[XmlInclude(typeof(Motion))]
    [Serializable]
	public class Shape : XObject
	{
		private ObjectPath path = new ObjectPath();
		private XColor lineColor = new XColor();
		private XColor fillColor = new XColor();
		private string text = String.Empty;
		private XFont textFont = new XFont("Arial", 8.5f);
		private bool stretchText = true;
        private PointF controlPoint = new PointF();
        private LineOptions lineOpts = new LineOptions();

		[XmlElement]
		public ObjectPath Path
		{
			get { return path; }
			set { path = value; }
		}
        
		[XmlElement]
		public XColor LineColor
		{
			get { return lineColor; }
			set { lineColor = value; }
		}

		[XmlElement]
		public XColor FillColor
		{
			get { return fillColor; }
			set { fillColor = value; }
		}

		[XmlElement]
		public string Text
		{
			get { return text; }
			set { text = value; }
		}

		[XmlElement]
		public XFont TextFont
		{
			get { return textFont; }
			set { textFont = value; }
		}

		[XmlElement]
		public bool StretchText
		{
			get { return stretchText; }
			set { stretchText = value; }
		}

        [XmlElement]
        public PointF ControlPoint
        {
            get { return controlPoint; }
            set { controlPoint = value; }
        }

        [XmlElement]
        public LineOptions LineSettings
        {
            get { return lineOpts; }
            set { lineOpts = value; }
        }

		[XmlIgnore]
		public override RectangleF Bounds
		{
			get { return GetBounds(); }
		}

        //Override for ShapeBezier
		public virtual void DrawControlPoints(Graphics g)
		{
            // draw white circle at controlPoint, only visible with PointEdit tool
            float zoom = g.Transform.Elements[0];

            RectangleF rect = new RectangleF(controlPoint.X - (4.0f / zoom), 
                controlPoint.Y - (4.0f / zoom), (8.0f / zoom), (8.0f / zoom));
            Pen p = new Pen(Color.Black, (1.0f / zoom));

            Brush b = new SolidBrush(Color.White);
            g.FillEllipse(b, rect);
            g.DrawEllipse(p, rect.X, rect.Y, rect.Width, rect.Height);
		}

        public virtual GraphicsPath CreateShapePath() { return new GraphicsPath(); }
        //TODO: move this to XObject or ObjectPath
        public virtual ObjectPath GetPathFromGP(GraphicsPath gp)
        {
            ObjectPath op = new ObjectPath();
            for (int i = 0; i < gp.PathPoints.Length; i++)
            {
                PathPoint p = new PathPoint(gp.PathPoints[i]);
                op.Points.Add(p);
            }
            return op;
            //OLD - FOR TEXT
            //XGroup group = new XGroup();
            //ShapeBezier bez = new ShapeBezier();

            //this.Path.Points.Clear();
            //for (int i = 0; i < gp.PathPoints.Length; i++)
            //{
            //    bool pointComplete = false;
            //    PathPoint p = new PathPoint();
            //    while (!pointComplete)
            //    {
            //        switch ((PathPointType)gp.PathTypes[i])
            //        {
            //            case PathPointType.Start:     //start of figure
            //                break;
            //            case PathPointType.Bezier:     //point of a segment
            //                break;
            //            // case PathPointType.Bezier3:     //endpoint or control point
            //            //    break;
            //            case PathPointType.CloseSubpath:
            //                break;
            //            case PathPointType.DashMode:  //marker
            //                break;
            //            case PathPointType.Line:  //end of figure
            //                break;
            //            case PathPointType.PathMarker:
            //                break;
            //            case PathPointType.PathTypeMask:
            //                group.Children.Add(bez);
            //                bez = new ShapeBezier();
            //                break;
            //            default:
            //                break;
            //        }
            //    }
            //    this.Path.Points.Add(p);
            //}

            //return group;
        }

        public override void DrawPathMarkers(Graphics g, List<PathPoint> selMarkers)
        {
            for (int i = 0; i < path.Points.Count; i++)
            {
                bool isSelected = false;
                if (selMarkers != null) { isSelected = selMarkers.IndexOf(path.Points[i]) >= 0; }
                DrawMarker(g, path.Points[i].Point, isSelected);
            }
        }

        private void DrawMarker(Graphics g, PointF pt, bool isSelected)
        {
            float zoom = g.Transform.Elements[0];

            RectangleF rect = new RectangleF(pt.X - (3.5f / zoom),
                pt.Y - (3.5f / zoom), (7.0f / zoom), (7.0f / zoom));
            Pen p = new Pen(Color.Black, (1.0f / zoom));

            Brush b = null;
			if (isSelected) { b = new SolidBrush(Color.Red); }
			else { b = new SolidBrush(Color.Gray); }

            g.FillRectangle(b, rect);
            g.DrawRectangle(p, rect.X, rect.Y, rect.Width, rect.Height);
        }

        private RectangleF GetBounds()
		{
			RectangleF rect = new RectangleF();

			if (path.Points.Count > 0)
			{
				PointF start = new PointF(path.Points[0].Point.X, path.Points[0].Point.Y);
				PointF end = new PointF(path.Points[path.Points.Count - 1].Point.X, path.Points[path.Points.Count - 1].Point.Y);
				List<PathPoint> points = path.Points;

				for (int i = 0; i < points.Count; i++)
				{
					if (points[i].Point.X < start.X) { start.X = points[i].Point.X; }
					if (points[i].Point.Y < start.Y) { start.Y = points[i].Point.Y; }
					if (points[i].Point.X > end.X) { end.X = points[i].Point.X; }
					if (points[i].Point.Y > end.Y) { end.Y = points[i].Point.Y; }
				}
				rect.X = start.X;
				rect.Y = start.Y;
				rect.Width = end.X - start.X;
				rect.Height = end.Y - start.Y;
			}

			return rect;
		}

		public void AppendPoint(PointF p, bool replace)
		{
			if (replace)
			{
				if (path.Points.Count > 1) //replace last point
				{ 
					path.Points[path.Points.Count - 1].Point = p;
					path.Points[path.Points.Count - 1].ControlA = p;
					path.Points[path.Points.Count - 1].ControlB = p;
				} 
				else { path.Points.Add(new PathPoint(p)); }
			}
			else { path.Points.Add(new PathPoint(p)); }
		}

		public override void Move(PointF amount)
		{
			for (int i = 0; i < path.Points.Count; i++)
			{
				MovePoint(path.Points[i], amount);
			}
		}

		public virtual void MoveMarker(PointF amount, PathPoint marker)
		{
			if (marker != null)
			{
				MovePoint(marker, amount);
			}
		}

		private void MovePoint(PathPoint pathPoint, PointF amount)
		{
			PointF p = pathPoint.Point;
			PointF cA = pathPoint.ControlA;
			PointF cB = pathPoint.ControlB;
			p.X += amount.X;
			p.Y += amount.Y;
			cA.X += amount.X;
			cA.Y += amount.Y;
			cB.X += amount.X;
			cB.Y += amount.Y;
			pathPoint.Point = p;
			pathPoint.ControlA = cA;
			pathPoint.ControlB = cB;
		}

		public override void Resize(SizeDirection direction, PointF amount) //primarily for di-point shapes. others are overridden
        {
            if(path.Points.Count == 2)
            {
                switch (direction)
                {
                    case SizeDirection.East:
                        MovePoint(path.Points[1], new PointF(amount.X, 0));
                        break;
                    case SizeDirection.North:
                        MovePoint(path.Points[0], new PointF(0, amount.Y));
                        break;
                    case SizeDirection.NorthEast:
                        MovePoint(path.Points[0], new PointF(0, amount.Y));
                        MovePoint(path.Points[1], new PointF(amount.X, 0));
                        break;
                    case SizeDirection.NorthWest:
                        MovePoint(path.Points[0], new PointF(amount.X, amount.Y));
                        break;
                    case SizeDirection.South:
                        MovePoint(path.Points[1], new PointF(0, amount.Y));
                        break;
                    case SizeDirection.SouthEast:
                        MovePoint(path.Points[1], new PointF(amount.X, amount.Y));
                        break;
                    case SizeDirection.SouthWest:
                        MovePoint(path.Points[0], new PointF(amount.X, 0));
                        MovePoint(path.Points[1], new PointF(0, amount.Y));
                        break;
                    case SizeDirection.West:
                        MovePoint(path.Points[0], new PointF(amount.X, 0));
                        break;
                }
            }
        }

		public override bool HitTest(Graphics g, RectangleF r)
		{
			int hitCount = -1;	//for later, in case the path contains no points
			GraphicsPath gp = new GraphicsPath();
			gp.AddRectangle(r);

			for (int i = 0; i < path.Points.Count; i++)
			{
				if (gp.IsVisible(path.Points[i].Point, g))
				{
					if (hitCount == -1) { hitCount = 1; }
					else { hitCount++; }
				}
			}

			if (hitCount == path.Points.Count) { return true; }
			else { return false; }
		}

		public override int HitTestPathMarkers(Graphics g, PointF p)
		{
			float zoom = g.Transform.Elements[0];

			PointF[] points = path.GetPoints();
			GraphicsPath gp = new GraphicsPath();

			for (int i = 0; i < points.Length; i++)
			{
				RectangleF rect = new RectangleF(points[i].X - (3.5f / zoom), points[i].Y - (3.5f / zoom), (7.0f / zoom), (7.0f / zoom));

				gp.AddRectangle(rect);

				if (gp.IsVisible(p)) { return i; }
				gp.ClearMarkers();
			}

			return -1;
		}



        public override void FlipHoriz(float offset)
        {
            Matrix m = new Matrix(-1, 0, 0, 1, 0, 0);
			m.Translate(offset, 0, MatrixOrder.Append);

            TransformPoints(m);
        }

        public override void FlipVert(float offset)
        {
            Matrix m = new Matrix(1, 0, 0, -1, 0, 0);
            //m.Translate((this.Bounds.Left * 2) + this.Bounds.Width, 0, MatrixOrder.Append);
			m.Translate(0, offset, MatrixOrder.Append);

            TransformPoints(m);
        }

        private void TransformPoints(Matrix m)
        {
            for (int i = 0; i < this.Path.Points.Count; i++)
            {
                PointF[] pts = new PointF[3]{this.Path.Points[i].Point, this.Path.Points[i].ControlA,
                    this.Path.Points[i].ControlB};
                m.TransformPoints(pts);
                this.Path.Points[i].Point = pts[0];
                this.Path.Points[i].ControlA = pts[1];
                this.Path.Points[i].ControlB = pts[2];
            }
        }

        public override ShapeBezier ConvertToPath()
        {
            GraphicsPath gp = CreateShapePath();    //overridden for most shapes
            ObjectPath op = GetPathFromGP(gp);      //overridden for text and ellipses only

            ShapeBezier bz = new ShapeBezier();
            bz.ControlPoint = this.ControlPoint;
            bz.FillColor = this.FillColor;
            bz.LineColor = this.LineColor;
            bz.LineSettings = this.LineSettings;
            bz.Motions = this.Motions;
            bz.Name = this.Name;
            bz.Type = this.Type;
            bz.Path = op;

            return bz;
        }
	}

    [Serializable]
	public class ShapeRectangle : Shape
	{
		public override void Draw(Graphics g)
		{
			g.FillRectangle(this.FillColor.GetBrush(this.Bounds), this.Bounds);
			g.DrawRectangle(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), this.Bounds.X, this.Bounds.Y, this.Bounds.Width, this.Bounds.Height);
		}

		public override void DrawGhost(Graphics g)
		{
			Pen p = new Pen(Color.Black, 1.0f);
			p.DashStyle = DashStyle.Dash;

			g.DrawRectangle(p, this.Bounds.X, this.Bounds.Y, this.Bounds.Width, this.Bounds.Height);
		}

		public override bool HitTest(Graphics g, PointF p)
		{
			GraphicsPath gp = new GraphicsPath();
			RectangleF rect = this.Bounds;
            gp.AddRectangle(rect);

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

			return false;
		}

        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            gp.AddRectangle(this.Bounds);

            return gp;
        }
	}

    [Serializable]
	public class ShapeSelect : Shape
	{
		public override void Draw(Graphics g)
		{
			DrawGhost(g);
		}

		public override void DrawGhost(Graphics g)
		{
			Pen p = new Pen(Color.Black, 1.0f);
			p.DashStyle = DashStyle.Dash;

			g.DrawRectangle(p, this.Bounds.X, this.Bounds.Y, this.Bounds.Width, this.Bounds.Height);
		}
	}

    [Serializable]
	public class ShapeEllipse : Shape
	{
		public override void Draw(Graphics g)
		{
			g.FillEllipse(this.FillColor.GetBrush(this.Bounds), this.Bounds);
            g.DrawEllipse(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), this.Bounds);	
		}

		public override void DrawGhost(Graphics g)
		{
			Pen p = new Pen(Color.Black, 1.0f);
			p.DashStyle = DashStyle.Dash;

			g.DrawEllipse(p, this.Bounds);
		}

		public override bool HitTest(Graphics g, PointF p)
		{
			GraphicsPath path = new GraphicsPath();
			RectangleF rect = this.Bounds;
			path.AddEllipse(rect);

			if (path.IsVisible(p)) { return true; }
			if (path.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

			return false;
		}

        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            gp.AddEllipse(this.Bounds);

            return gp;
        }

        public override ObjectPath GetPathFromGP(GraphicsPath gp)
        {
            return EllipseToPath(this.Bounds);
        }

        // Create points to simulate ellipse using beziers
        private ObjectPath EllipseToPath(RectangleF rect)
        {
            PathPoint[] pts = new PathPoint[5];
            ObjectPath op = new ObjectPath();

            //"Magical Constant"	2/3*(sqrt(2)-1) 
            //http://www.codeguru.com/Cpp/G-M/gdi/article.php/c131
            const double EToBConst = 0.2761423749154;

            SizeF offset = new SizeF(rect.Width * (float)EToBConst, rect.Height * (float)EToBConst);
            PointF center = new PointF((rect.Left + rect.Right) / 2, (rect.Top + rect.Bottom) / 2);

            pts[0] = new PathPoint();
            pts[0].ControlA = new PointF(rect.Left, center.Y);	//ignored
            pts[0].Point = new PointF(rect.Left, center.Y);
            pts[0].ControlB = new PointF(rect.Left, center.Y - offset.Height);

            pts[1] = new PathPoint();
            pts[1].ControlA = new PointF(center.X - offset.Width, rect.Top);
            pts[1].Point = new PointF(center.X, rect.Top);
            pts[1].ControlB = new PointF(center.X + offset.Width, rect.Top);

            pts[2] = new PathPoint();
            pts[2].ControlA = new PointF(rect.Right, center.Y - offset.Height);
            pts[2].Point = new PointF(rect.Right, center.Y);
            pts[2].ControlB = new PointF(rect.Right, center.Y + offset.Height);

            pts[3] = new PathPoint();
            pts[3].ControlA = new PointF(center.X + offset.Width, rect.Bottom);
            pts[3].Point = new PointF(center.X, rect.Bottom);
            pts[3].ControlB = new PointF(center.X - offset.Width, rect.Bottom);

            pts[4] = new PathPoint();
            pts[4].ControlA = new PointF(rect.Left, center.Y + offset.Height);
            pts[4].Point = new PointF(rect.Left, center.Y);
            pts[4].ControlB = new PointF(rect.Left, center.Y);	//ignored

            op.Points.AddRange(pts);

            return op;
        }
	}

    [Serializable]
	public class ShapeLine : Shape
	{
		public override void Draw(Graphics g)
		{
			if (Path.Points.Count > 1)
			{
                g.DrawLine(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), this.Path.Points[0].Point, this.Path.Points[1].Point);
			}
		}

		public override void DrawGhost(Graphics g)
		{
			if (Path.Points.Count > 1)
			{
				Pen p = new Pen(Color.Black, 1.0f);
				p.DashStyle = DashStyle.Dash;

				g.DrawLine(p, this.Path.Points[0].Point, this.Path.Points[1].Point);
			}
		}

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();

            RectangleF rect = this.Bounds;
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, Math.Max(this.LineSettings.Width, 5)))) { return true; }

            return false;
        }

        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            gp.AddLine(this.Path.Points[0].Point, this.Path.Points[1].Point);

            return gp;
        }
	}

    [Serializable]
	public class ShapeBezier : Shape
	{
		public override void Draw(Graphics g)
		{
			if (Path.Points.Count > 1)
			{
				GraphicsPath gp = new GraphicsPath();
                gp.AddBeziers(this.Path.GetBezierPoints());
                Pen p = this.LineColor.GetPen(this.Bounds, this.LineSettings.Width);

				g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
				g.DrawPath(p, gp);
			}
		}

		public override void DrawGhost(Graphics g)
		{
			if (Path.Points.Count > 1)
			{
				GraphicsPath gp = new GraphicsPath();
                gp.AddBeziers(this.Path.GetBezierPoints());
				Pen p = new Pen(Color.Black, 1.0f);
				p.DashStyle = DashStyle.Dash;

				g.DrawPath(p, gp);
			}
		}

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = new GraphicsPath();
            gp.AddBeziers(this.Path.GetBezierPoints());
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }

        //TODO: combine these two rotate functions
        public override void Rotate(PointF centerPoint, PointF controlPoint)
        {
            Matrix m = new Matrix();
            m.RotateAt(GetAngle(controlPoint, centerPoint), centerPoint);

            for (int i = 0; i < this.Path.Points.Count; i++)
            {
                PointF[] pts = new PointF[3]{this.Path.Points[i].Point, this.Path.Points[i].ControlA,
                    this.Path.Points[i].ControlB};
                m.TransformPoints(pts);
                this.Path.Points[i].Point = pts[0];
                this.Path.Points[i].ControlA = pts[1];
                this.Path.Points[i].ControlB = pts[2];
            }
        }

        public override void Rotate(float angle)
        {
            Matrix m = new Matrix();
            m.RotateAt(angle, this.CenterPoint);

            for (int i = 0; i < this.Path.Points.Count; i++)
            {
                PointF[] pts = new PointF[3]{this.Path.Points[i].Point, this.Path.Points[i].ControlA,
                    this.Path.Points[i].ControlB};
                m.TransformPoints(pts);
                this.Path.Points[i].Point = pts[0];
                this.Path.Points[i].ControlA = pts[1];
                this.Path.Points[i].ControlB = pts[2];
            }
        }

        //TODO: WHY?
        //This is more accurate and doesn't move wildly when it's positioned at 0,0

        public override void Resize(SizeDirection direction, PointF amount)
        {
            GraphicsPath gp = new GraphicsPath();
            gp.AddBeziers(this.Path.GetBezierPoints());

            Matrix m = new Matrix();
            m.Scale(1 + amount.X / this.Bounds.Width, 1 + amount.Y / this.Bounds.Height);

            for (int i = 0; i < this.Path.Points.Count; i++)
            {
                PointF[] pts = new PointF[3]{this.Path.Points[i].Point, this.Path.Points[i].ControlA,
                    this.Path.Points[i].ControlB};
                m.TransformPoints(pts);
                this.Path.Points[i].Point = pts[0];
                this.Path.Points[i].ControlA = pts[1];
                this.Path.Points[i].ControlB = pts[2];
            }

            //TODO: move to keep bounds.? locked in position
        }

        //public override void Resize(SizeDirection direction, PointF amount) //primarily for di-point shapes. others are overridden
        //{
        //    if (path.Points.Count == 2)
        //    {
        //        switch (direction)
        //        {
        //            case SizeDirection.East:
        //                MovePoint(path.Points[1], new PointF(amount.X, 0));
        //                break;
        //            case SizeDirection.North:
        //                MovePoint(path.Points[0], new PointF(0, amount.Y));
        //                break;
        //            case SizeDirection.NorthEast:
        //                MovePoint(path.Points[0], new PointF(0, amount.Y));
        //                MovePoint(path.Points[1], new PointF(amount.X, 0));
        //                break;
        //            case SizeDirection.NorthWest:
        //                MovePoint(path.Points[0], new PointF(amount.X, amount.Y));
        //                break;
        //            case SizeDirection.South:
        //                MovePoint(path.Points[1], new PointF(0, amount.Y));
        //                break;
        //            case SizeDirection.SouthEast:
        //                MovePoint(path.Points[1], new PointF(amount.X, amount.Y));
        //                break;
        //            case SizeDirection.SouthWest:
        //                MovePoint(path.Points[0], new PointF(amount.X, 0));
        //                MovePoint(path.Points[1], new PointF(0, amount.Y));
        //                break;
        //            case SizeDirection.West:
        //                MovePoint(path.Points[0], new PointF(amount.X, 0));
        //                break;
        //        }
        //    }
        //}

		public void DrawBezierControls(Graphics g, int segment)
		{
			//only draw them if
				// A) it's not the first or last point
				// B) the control point is not equal to the marker point
            if (segment >= 0 && Path.Points.Count > segment + 1)
            {
                float zoom = g.Transform.Elements[0];

                if (Path.Points[segment].ControlB != Path.Points[segment].Point)
                {
                    RectangleF r = GetControlRect(Path.Points[segment].ControlB, zoom);

                    g.DrawEllipse(new Pen(Color.Black, 1.0f / zoom), r);
                    g.FillEllipse(new SolidBrush(Color.Red), r);
                }

                if (Path.Points[segment + 1].ControlA != Path.Points[segment + 1].Point)
                {
                    RectangleF r = GetControlRect(Path.Points[segment + 1].ControlA, zoom);

                    g.DrawEllipse(new Pen(Color.Black, 1.0f / zoom), r);
                    g.FillEllipse(new SolidBrush(Color.Red), r);
                }
            }
		}

        //control index is eithr 0 or 1, indicating A or B
        public void MoveBezierControl(PathPoint point, PointF amount, int controlIndex)
        {
            if (controlIndex == 0)
            {
                PointF p = point.ControlB;
                p.X += amount.X;
                p.Y += amount.Y;

                point.ControlB = p;
            }
            else if (controlIndex == 1)
            {
                PointF p = point.ControlA;
                p.X += amount.X;
                p.Y += amount.Y;

                point.ControlA = p;
            }
        }

        private RectangleF GetControlRect(PointF p, float zoom)
        {
            RectangleF r = new RectangleF();
            r.Location = p;
            r.Inflate(5 / zoom, 5 / zoom);

            return r;
        }

        public void InitializeControlPoints(int segment)   //0 based segment index
        {
            //if the control points for this segment = path points && object type = bezier, then..
            //move the control points so they don't overlap the path.
            if (Path.Points.Count > segment + 1)
            {
                if (Path.Points[segment].Point == Path.Points[segment].ControlB)
                {
                    Path.Points[segment].ControlB = Path.GetFractionalPoint(Path.Points[segment].Point, Path.Points[segment + 1].Point, .334f);
                }

                if (Path.Points[segment + 1].Point == Path.Points[segment + 1].ControlA)
                {
                    Path.Points[segment + 1].ControlA = Path.GetFractionalPoint(Path.Points[segment].Point, Path.Points[segment + 1].Point, .667f);
                }
            }
        }

        public int HitTestBezierControls(Graphics g, PointF p, int segment)
        {
            if (Path.Points.Count > segment + 1)
            {
                float zoom = g.Transform.Elements[0];
                RectangleF r1 = GetControlRect(Path.Points[segment].ControlB, zoom);
                RectangleF r2 = GetControlRect(Path.Points[segment + 1].ControlA, zoom);

                GraphicsPath gp = new GraphicsPath();
                gp.AddEllipse(r1);
                if (gp.IsVisible(p, g)) { return 0; }

                gp.ClearMarkers();
                gp.AddEllipse(r2);
                if (gp.IsVisible(p, g)) { return 1; }
            }
            return -1;
        }

        public int HitTestSegment(Graphics g, PointF p)
        {
            for (int i = 0; i < Path.Points.Count - 1; i++)
            {
                float zoom = g.Transform.Elements[0];
                float lineWidth = Math.Max(this.LineSettings.Width, 3) / zoom;
                GraphicsPath gp = new GraphicsPath();
                gp.AddBezier(Path.Points[i].Point, Path.Points[i].ControlB,
                    Path.Points[i + 1].ControlA, Path.Points[i + 1].Point);
                if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, lineWidth), g))
                { return i; }
            }

            return -1;
        }

        public override ShapeBezier ConvertToPath()
        {
            return null; //already a path
        }
    }

    [Serializable]
	public class ShapeFreeHand : Shape
	{
		public override void Draw(Graphics g)
		{
			if (Path.Points.Count > 1)
			{
                GraphicsPath gp = CreateShapePath();
                Pen p = this.LineColor.GetPen(this.Bounds, this.LineSettings.Width);
				//gp.Widen(p);	//smoother

				g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
				g.DrawPath(p, gp);
			}
		}

		public override void DrawGhost(Graphics g)
		{
			if (Path.Points.Count > 1)
			{
                GraphicsPath gp = CreateShapePath();
				Pen p = new Pen(Color.Black, 1.0f);
				p.DashStyle = DashStyle.Dash;

				g.DrawPath(p, gp);
			}
		}

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }
        
            return false;
        }

        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            gp.AddLines(this.Path.GetPoints());

            return gp;
        }
	}

    [Serializable]
	public class ShapeSpray : Shape
	{
		public override void Draw(Graphics g)
		{
			
		}

		public override void DrawGhost(Graphics g)
		{
			base.DrawGhost(g);
		}
	}

    [Serializable]
	public class ShapeText : Shape
	{
		public override void Draw(Graphics g)
		{
			Pen p = this.LineColor.GetPen(this.Bounds, this.LineSettings.Width);
			Brush b = this.FillColor.GetBrush(this.Bounds);

            if (!String.IsNullOrEmpty(this.Text)) { DrawText(g, p, b); }
		}

		public override void DrawGhost(Graphics g)
		{
			Pen p = new Pen(Color.Blue, 1.0f);
			p.DashStyle = DashStyle.Dash;

            if (!String.IsNullOrEmpty(this.Text)) { DrawText(g, p, null); }

			g.DrawRectangle(p, this.Bounds.X, this.Bounds.Y, this.Bounds.Width, this.Bounds.Height);
		}

        public override void DrawTextMarker(Graphics g, int position) 
        {
            Pen p = new Pen(Color.Blue, 1.0f);
            p.DashStyle = DashStyle.Dash;
        }

		private void DrawText(Graphics g, Pen p, Brush b)
		{
            GraphicsPath gp = CreateShapePath();
			g.DrawPath(p, gp);

			if (b != null)
			{
				g.FillPath(b, gp);
			}
		}

        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            gp.AddString(this.Text, this.TextFont.GetFamily(), 0, this.TextFont.Size, this.Path.Points[0].Point, StringFormat.GenericDefault);
            Matrix m;

            //g.DrawString(this.Text, this.TextFont, this.FillColor.GetBrush(this.Bounds), this.Path.Points[0]);
            //g.DrawLine(p, this.Path.Points[0], this.Path.Points[1]);
            if (this.StretchText)
            {
                PointF[] mPts = new PointF[3]{this.Bounds.Location, new PointF(this.Bounds.Right, this.Bounds.Top),
				new PointF(this.Bounds.Left, this.Bounds.Bottom)};
                m = new Matrix(gp.GetBounds(), mPts);

                gp.Transform(m);
            }

            return gp;
        }

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }

        //public override ShapeBezier ConvertToPath()
        //{
        //    GraphicsPath gp = new GraphicsPath();
        //    gp.AddString(this.Text, this.TextFont.GetFamily(), 0, this.TextFont.Size, this.Path.Points[0].Point, StringFormat.GenericDefault);
        //    Matrix m;

        //    //g.DrawString(this.Text, this.TextFont, this.FillColor.GetBrush(this.Bounds), this.Path.Points[0]);
        //    //g.DrawLine(p, this.Path.Points[0], this.Path.Points[1]);
        //    if (this.StretchText)
        //    {
        //        PointF[] mPts = new PointF[3]{this.Bounds.Location, new PointF(this.Bounds.Right, this.Bounds.Top),
        //        new PointF(this.Bounds.Left, this.Bounds.Bottom)};
        //        m = new Matrix(gp.GetBounds(), mPts);

        //        gp.Transform(m);
        //    }

        //    XObject group = PathFromGraphicsPath(gp);

        //    ShapeBezier bz = new ShapeBezier();
        //    bz.ControlPoint = this.ControlPoint;
        //    bz.FillColor = this.FillColor;
        //    bz.LineColor = this.LineColor;
        //    bz.LineSettings = this.LineSettings;
        //    bz.Motions = this.Motions;
        //    bz.Name = this.Name;
        //    bz.Type = this.Type;
        //    bz.Path = this.Path;

        //    //TODO: modify to return XObject. Will be ShapeBezier in most cases
        //    //but XGroup for text.
        //    return bz;
        //}
	}

    [Serializable]
	public class ShapeShadow : Shape
	{
		public override void Draw(Graphics g)
		{
			g.DrawString(this.Text, this.TextFont.GetFont(), this.FillColor.GetBrush(this.Bounds), this.Path.Points[0].Point);
		}
	}

    [Serializable]
	public class ShapeClone : Shape     //unused
	{
		public override void Draw(Graphics g)
		{
			g.DrawString(this.Text, this.TextFont.GetFont(), this.FillColor.GetBrush(this.Bounds), this.Path.Points[0].Point);
		}
	}

    [Serializable]
	public class ShapePattern : Shape       //unused
	{
		public override void Draw(Graphics g)
		{
			g.DrawString(this.Text, this.TextFont.GetFont(), this.FillColor.GetBrush(this.Bounds), this.Path.Points[0].Point);
		}
	}

    [Serializable]
	public class ShapeZoom : Shape      //unused
	{
		public override void Draw(Graphics g)
		{
			g.DrawString(this.Text, this.TextFont.GetFont(), this.FillColor.GetBrush(this.Bounds), this.Path.Points[0].Point);
		}
	}

    [Serializable]
    public class ShapeFivePtStar : Shape
    {
        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            float thirdX = (this.Bounds.Right - this.Bounds.Left) / 3.0f;
            PointF[] pts = new PointF[5]
            {
                new PointF(this.Bounds.Left + thirdX, this.Bounds.Top),
                new PointF(this.Bounds.Right, this.Bounds.Top),
                new PointF(this.Bounds.Right - thirdX, this.Bounds.Bottom),
                new PointF(this.Bounds.Left, this.Bounds.Bottom),
                new PointF(this.Bounds.Left + thirdX, this.Bounds.Top)
            };

            gp.AddLines(pts);
            return gp;
        }

        public override void Draw(Graphics g)
        {
            GraphicsPath gp = CreateShapePath();
            g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
            g.DrawPath(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), gp);
        }

        public override void DrawGhost(Graphics g)
        {
            Pen p = new Pen(Color.Black, 1.0f);
            p.DashStyle = DashStyle.Dash;

            GraphicsPath gp = CreateShapePath();
            g.DrawPath(p, gp);
        }

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }
    }

    [Serializable]
    public class ShapeSixPtStar: Shape
    {
        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            PointF[] pts = new PointF[4]
            {
                new PointF(this.Bounds.Left, this.Bounds.Top),
                new PointF(this.Bounds.Right, this.Bounds.Bottom),
                new PointF(this.Bounds.Left, this.Bounds.Bottom),
                new PointF(this.Bounds.Left, this.Bounds.Top)   
            };

            gp.AddLines(pts);
            return gp;
        }

        public override void Draw(Graphics g)
        {
            GraphicsPath gp = CreateShapePath();
            g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
            g.DrawPath(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), gp);
        }

        public override void DrawGhost(Graphics g)
        {
            Pen p = new Pen(Color.Black, 1.0f);
            p.DashStyle = DashStyle.Dash;

            GraphicsPath gp = CreateShapePath();
            g.DrawPath(p, gp);
        }

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }
    }

    [Serializable]
    public class ShapeRightTriangle : Shape
    {
        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            PointF[] pts = new PointF[4]
            {
                new PointF(this.Bounds.Left, this.Bounds.Top),
                new PointF(this.Bounds.Right, this.Bounds.Bottom),
                new PointF(this.Bounds.Left, this.Bounds.Bottom),
                new PointF(this.Bounds.Left, this.Bounds.Top)   
            };

            gp.AddLines(pts);
            return gp;
        }

        public override void Draw(Graphics g)
        {
            GraphicsPath gp = CreateShapePath();
            g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
            g.DrawPath(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), gp);
        }

        public override void DrawGhost(Graphics g)
        {
            Pen p = new Pen(Color.Black, 1.0f);
            p.DashStyle = DashStyle.Dash;

            GraphicsPath gp = CreateShapePath();
            g.DrawPath(p, gp);
        }

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }
    }

    [Serializable]
    public class ShapeIsoscelesTriangle : Shape
    {
        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            PointF[] pts = new PointF[4]
            {
                new PointF((this.Bounds.Left + this.Bounds.Right) / 2.0f, this.Bounds.Top),
                new PointF(this.Bounds.Right, this.Bounds.Bottom),
                new PointF(this.Bounds.Left, this.Bounds.Bottom),
                new PointF((this.Bounds.Left + this.Bounds.Right) / 2.0f, this.Bounds.Top)   
            };

            gp.AddLines(pts);
            return gp;
        }

        public override void Draw(Graphics g)
        {
            GraphicsPath gp = CreateShapePath();
            g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
            g.DrawPath(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), gp);
        }

        public override void DrawGhost(Graphics g)
        {
            Pen p = new Pen(Color.Black, 1.0f);
            p.DashStyle = DashStyle.Dash;

            GraphicsPath gp = CreateShapePath();
            g.DrawPath(p, gp);
        }

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }
    }

    [Serializable]
    public class ShapePentagon: Shape
    {
        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            PointF[] pts = new PointF[6];
            float a = 0;
            for (int i = 0; i < 5; i++)
            {
                pts[i] = new PointF(
                    this.Bounds.Left + ((this.Bounds.Width / 2.0f) * (float)Math.Cos(a)) + this.Bounds.Width / 2.0f, 
                    this.Bounds.Top + ((this.Bounds.Height / 2.0f) * (float)Math.Sin(a)) + this.Bounds.Height / 2.0f );
                a += (float)Math.PI * 2.0f / 5.0f;
            }
            //pts[5] = pts[0];

            gp.AddLines(pts);
            return gp;
        }

        public override void Draw(Graphics g)
        {
            GraphicsPath gp = CreateShapePath();
            g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
            g.DrawPath(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), gp);
        }

        public override void DrawGhost(Graphics g)
        {
            Pen p = new Pen(Color.Black, 1.0f);
            p.DashStyle = DashStyle.Dash;

            GraphicsPath gp = CreateShapePath();
            g.DrawPath(p, gp);
        }

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }
    }

    [Serializable]
    public class ShapeHexagon : Shape
    {
        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            float quarterX = (this.Bounds.Right - this.Bounds.Left) / 4.0f;
            float halfY = (this.Bounds.Bottom - this.Bounds.Top) / 2.0f;

            PointF[] pts = new PointF[7]
            {
                new PointF(this.Bounds.Left + quarterX, this.Bounds.Top),
                new PointF(this.Bounds.Right - quarterX, this.Bounds.Top),
                new PointF(this.Bounds.Right, halfY),
                new PointF(this.Bounds.Right - quarterX, this.Bounds.Bottom), 
                new PointF(this.Bounds.Left + quarterX, this.Bounds.Bottom),
                new PointF(this.Bounds.Left, halfY),
                new PointF(this.Bounds.Left + quarterX, this.Bounds.Top)
            };

            gp.AddLines(pts);
            return gp;
        }

        public override void Draw(Graphics g)
        {
            GraphicsPath gp = CreateShapePath();
            g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
            g.DrawPath(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), gp);
        }

        public override void DrawGhost(Graphics g)
        {
            Pen p = new Pen(Color.Black, 1.0f);
            p.DashStyle = DashStyle.Dash;

            GraphicsPath gp = CreateShapePath();
            g.DrawPath(p, gp);
        }

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }
    }

    [Serializable]
    public class ShapeOctagon : Shape
    {
        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            float thirdX = (this.Bounds.Right - this.Bounds.Left) / 3.0f;
            float thirdY = (this.Bounds.Bottom - this.Bounds.Top) / 3.0f;

            PointF[] pts = new PointF[9]
            {
                new PointF(this.Bounds.Left + thirdX, this.Bounds.Top),
                new PointF(this.Bounds.Right - thirdX, this.Bounds.Top),
                new PointF(this.Bounds.Right, this.Bounds.Top + thirdY),
                new PointF(this.Bounds.Right, this.Bounds.Bottom - thirdY),
                new PointF(this.Bounds.Right - thirdX, this.Bounds.Bottom),
                new PointF(this.Bounds.Left + thirdX, this.Bounds.Bottom),
                new PointF(this.Bounds.Left, this.Bounds.Bottom - thirdY),
                new PointF(this.Bounds.Left, this.Bounds.Top + thirdY),
                new PointF(this.Bounds.Left + thirdX, this.Bounds.Top) 
            };

            gp.AddLines(pts);
            return gp;
        }

        public override void Draw(Graphics g)
        {
            GraphicsPath gp = CreateShapePath();
            g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
            g.DrawPath(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), gp);
        }

        public override void DrawGhost(Graphics g)
        {
            Pen p = new Pen(Color.Black, 1.0f);
            p.DashStyle = DashStyle.Dash;

            GraphicsPath gp = CreateShapePath();
            g.DrawPath(p, gp);
        }

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }
    }

    [Serializable]
    public class ShapeParallelogram : Shape
    {
        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            float thirdX = (this.Bounds.Right - this.Bounds.Left) / 3.0f;
            PointF[] pts = new PointF[5]
            {
                new PointF(this.Bounds.Left + thirdX, this.Bounds.Top),
                new PointF(this.Bounds.Right, this.Bounds.Top),
                new PointF(this.Bounds.Right - thirdX, this.Bounds.Bottom),
                new PointF(this.Bounds.Left, this.Bounds.Bottom),
                new PointF(this.Bounds.Left + thirdX, this.Bounds.Top)
            };

            gp.AddLines(pts);
            return gp;
        }

        public override void Draw(Graphics g)
        {
            GraphicsPath gp = CreateShapePath();
            g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
            g.DrawPath(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), gp);
        }

        public override void DrawGhost(Graphics g)
        {
            Pen p = new Pen(Color.Black, 1.0f);
            p.DashStyle = DashStyle.Dash;

            GraphicsPath gp = CreateShapePath();
            g.DrawPath(p, gp);
        }

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }
    }

    [Serializable]
    public class ShapeTrapezoid : Shape
    {
        public override GraphicsPath CreateShapePath()
        {
            GraphicsPath gp = new GraphicsPath();
            float thirdX = (this.Bounds.Right - this.Bounds.Left) / 3.0f;
            PointF[] pts = new PointF[5]
            {
                new PointF(this.Bounds.Left, this.Bounds.Top),
                new PointF(this.Bounds.Right, this.Bounds.Top),
                new PointF(this.Bounds.Right - thirdX, this.Bounds.Bottom),
                new PointF(this.Bounds.Left - thirdX, this.Bounds.Bottom),
                new PointF(this.Bounds.Left, this.Bounds.Top)
            };

            gp.AddLines(pts);
            return gp;
        }

        public override void Draw(Graphics g)
        {
            GraphicsPath gp = CreateShapePath();
            g.FillPath(this.FillColor.GetBrush(this.Bounds), gp);
            g.DrawPath(this.LineColor.GetPen(this.Bounds, this.LineSettings.Width), gp);
        }

        public override void DrawGhost(Graphics g)
        {
            Pen p = new Pen(Color.Black, 1.0f);
            p.DashStyle = DashStyle.Dash;

            GraphicsPath gp = CreateShapePath();
            g.DrawPath(p, gp);
        }

        public override bool HitTest(Graphics g, PointF p)
        {
            GraphicsPath gp = CreateShapePath();
            RectangleF rect = this.Bounds;

            if (gp.IsVisible(p)) { return true; }
            if (gp.IsOutlineVisible(p, this.LineColor.GetPen(this.Bounds, this.LineSettings.Width))) { return true; }

            return false;
        }
    }
}
